package cn.jtcoding.springframework.core;

import cn.jtcoding.springframework.core.annotation.Component;
import cn.jtcoding.springframework.core.annotation.ComponentScan;
import cn.jtcoding.springframework.core.annotation.Scope;
import cn.jtcoding.springframework.core.annotation.Service;
import cn.jtcoding.springframework.core.support.AnnotatedBeanDefinition;
import cn.jtcoding.springframework.core.support.BeanDefinition;
import cn.jtcoding.springframework.enums.ScopeEnum;
import cn.jtcoding.springframework.util.ClassUtils;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.lang.annotation.Annotation;
import java.util.*;
import java.util.function.Function;

import static cn.jtcoding.springframework.core.support.AbstractBeanDefinition.AUTOWIRE_BY_TYPE;

@Slf4j
public class AnnotationBeanDefinitionScanner {

    public final Set<String> nonBeanObjects = new HashSet<>(256);
    public final List<String> BEAN_ANNOTATION = Arrays.asList(Service.class.getName(), Component.class.getName());

    public Set<? extends BeanDefinition> scan(Class<?> clazz) {
        if (!clazz.isAnnotationPresent(ComponentScan.class)) {
            throw new IllegalArgumentException("ComponentScan annotation cannot be empty!");
        }
        String packageName = ClassUtils.resolvePackageName(clazz);
        Set<String> basePackagesSet = new LinkedHashSet<>();
        basePackagesSet.add(packageName);

        ComponentScan componentScan = clazz.getAnnotation(ComponentScan.class);
        if (componentScan.value().length > 0) {
            basePackagesSet.addAll(Arrays.asList(componentScan.value()));
        }
        return doScan(basePackagesSet.toArray(new String[0]));
    }

    private Set<? extends BeanDefinition> doScan(String... basePackages) {
        ClassLoader classLoader = ClassUtils.getDefaultClassLoader();
        Set<AnnotatedBeanDefinition> beanDefinitions = new LinkedHashSet<>();
        for (String basePackage : basePackages) {
            findCandidateComponents(basePackage, beanDefinitions, classLoader);
        }
        log.debug("non bean size: {}", nonBeanObjects.size());
        return beanDefinitions;
    }

    public void findCandidateComponents(String basePackage, Set<AnnotatedBeanDefinition> beanDefinitions, ClassLoader classLoader) {
        File file = ClassUtils.basePackageToFile(basePackage, classLoader);
        Function<File, Class<?>> fileToClass = ClassUtils.fileToClass(basePackage, classLoader);

        Set<File> classFileSet = new LinkedHashSet<>();
        ClassUtils.extractClassFile(classFileSet, file);
        for (File classFile : classFileSet) {
            Class<?> clazz = fileToClass.apply(classFile);
            if (!isCandidateComponent(clazz)) {
                nonBeanObjects.add(clazz.getName());
                continue;
            }
            AnnotatedBeanDefinition beanDefinition = constructAnnotatedBeanDefinition(clazz);
            beanDefinitions.add(beanDefinition);
        }
    }

    public boolean isCandidateComponent(Class<?> clazz) {
        Annotation[] annotations = clazz.getAnnotations();
        if (annotations.length == 0) {
            return false;
        }
        return Arrays.stream(annotations)
                .anyMatch(annotation -> BEAN_ANNOTATION.contains(annotation.annotationType().getName()));
    }

    public AnnotatedBeanDefinition constructAnnotatedBeanDefinition(Class<?> clazz) {
        AnnotatedBeanDefinition beanDefinition = new AnnotatedBeanDefinition();
        beanDefinition.setLazyInit(false);
        beanDefinition.setBeanClass(clazz);
        beanDefinition.setAutowireMode(AUTOWIRE_BY_TYPE);
        beanDefinition.setScope(ScopeEnum.SINGLETON);
        beanDefinition.setAutowireCandidate(true);
        beanDefinition.setAnnotations(clazz.getAnnotations());
        // TODO hold on
//        beanDefinition.setInitMethodName();
//        beanDefinition.setDestroyMethodName();

        if (clazz.isAnnotationPresent(Scope.class)) {
            Scope scope = clazz.getAnnotation(Scope.class);
            if (!scope.value().equals(ScopeEnum.SINGLETON)
                    && !scope.value().equals(ScopeEnum.PROTOTYPE)) {
                throw new IllegalArgumentException("Scope annotation value incorrect.");
            }
            if (scope.value().equals(ScopeEnum.PROTOTYPE)) {
                beanDefinition.setScope(ScopeEnum.PROTOTYPE);
            }
        }
        return beanDefinition;
    }
}
